package mylittleinvestigations.CaseInformation;

import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.xml.parsers.ParserConfigurationException;
import mylittleinvestigations.*;
import mylittleinvestigations.Conversation.NotificationAction;
import mylittleinvestigations.FieldCharacters.CharacterDirection;
import mylittleinvestigations.FieldCharacters.FieldCharacter;
import mylittleinvestigations.FieldCharacters.FieldCharacterDirection;
import mylittleinvestigations.FieldCharacters.FieldCharacterState;
import mylittleinvestigations.GameUi.GameUiLoader;
import mylittleinvestigations.PersistentThreads.RenderThread;
import org.dawnmew.jvirtualstream.VirtualFileSystem;
import org.newdawn.slick.SlickException;
import org.xml.sax.SAXException;

public class Case
{
    public static final String CommonFilesId = "CommonFiles"; 
    
    private static Case instance;
    private static Thread caseParsingThread;

    private ArrayList<DependencyCheck> dependencyCheckList;
    private HashMap<String, ArrayList<String>> parentLocationListsBySpriteSheetId;
    private HashMap<String, ArrayList<String>> parentLocationListsByVideoId;
    private final Object loadStageSyncObject = new Object();
    
    private String privateExponent;

    public Case()
    {
        this.animationManager = new AnimationManager();
        this.audioManager = new AudioManager();
        this.content = new Content();
        this.dialogCharacterManager = new DialogCharacterManager();
        this.dialogCutsceneManager = new DialogCutsceneManager();
        this.evidenceManager = new EvidenceManager();
        this.fieldCharacterManager = new FieldCharacterManager();
        this.fieldCutsceneManager = new FieldCutsceneManager();
        this.flagManager = new FlagManager();
        this.partnerManager = new PartnerManager();
        this.spriteManager = new SpriteManager();

        this.dependencyCheckList = new ArrayList<DependencyCheck>();
        this.parentLocationListsBySpriteSheetId = new HashMap<String, ArrayList<String>>();
        this.parentLocationListsByVideoId = new HashMap<String, ArrayList<String>>();
    }

    public static Case getInstance()
    {
        if (instance == null)
        {
            instance = new Case();
        }

        return instance;
    }

    private AnimationManager animationManager;

    public AnimationManager getAnimationManager() { return this.animationManager; }

    private AudioManager audioManager;

    public AudioManager getAudioManager() { return this.audioManager; }

    private Content content;

    public Content getContent() { return this.content; }

    private DialogCharacterManager dialogCharacterManager;

    public DialogCharacterManager getDialogCharacterManager() { return this.dialogCharacterManager; }

    private DialogCutsceneManager dialogCutsceneManager;

    public DialogCutsceneManager getDialogCutsceneManager() { return this.dialogCutsceneManager; }

    private EvidenceManager evidenceManager;

    public EvidenceManager getEvidenceManager() { return this.evidenceManager; }

    private FieldCharacterManager fieldCharacterManager;

    public FieldCharacterManager getFieldCharacterManager() { return this.fieldCharacterManager; }

    private FieldCutsceneManager fieldCutsceneManager;

    public FieldCutsceneManager getFieldCutsceneManager() { return this.fieldCutsceneManager; }

    private String filePath;

    public String getFilePath() { return this.filePath; }
    public void setFilePath(String filePath) { this.filePath = filePath; }

    private FlagManager flagManager;

    public FlagManager getFlagManager() { return this.flagManager; }
    
    private boolean isFinished;

    public boolean getIsFinished() { return this.isFinished; }
    public void setIsFinished(boolean isFinished) { this.isFinished = isFinished; }
    
    private String loadStage;
    
    public String getLoadStage()
    {
        String loadStage = null;
        
        synchronized (this.loadStageSyncObject)
        {
            loadStage = this.loadStage;
        }
        
        return loadStage;
    }
    
    public void setLoadStage(String loadStage)
    {
        synchronized (this.loadStageSyncObject)
        {
            this.loadStage = loadStage;
        }
    }

    private PartnerManager partnerManager;

    public PartnerManager getPartnerManager() { return this.partnerManager; }

    private SpriteManager spriteManager;

    public SpriteManager getSpriteManager() { return this.spriteManager; }
    
    private CaseMetadata metadata;
    
    public CaseMetadata getMetadata() { return this.metadata; }
    public void setMetadata(CaseMetadata metadata) { this.metadata = metadata; }

    public static void DiscardInstance()
    {
        String filePath = Case.getInstance().getFilePath();
        instance = new Case();
        instance.setFilePath(filePath);
    }
    
    public void AddLocationCharacterDependencyCheck(String characterId, CharacterDirection characterDirection, FieldCharacterDirection characterSpriteDirection, Vector2 characterPosition, String clickId, Vector2 interactionLocation, boolean interactFromAnywhere, Condition condition, Location location)
    {
        this.dependencyCheckList.add(new LocationCharacterDependencyCheck(characterId, characterDirection, characterSpriteDirection, characterPosition, clickId, interactionLocation, interactFromAnywhere, condition, location));
    }
    
    public void AddFieldCutsceneAddCharacterDependencyCheck(String characterId, String partnerCharacterId, CharacterDirection characterDirection, FieldCharacterDirection fieldCharacterDirection, Vector2 characterPosition, FieldCutscene cutscene)
    {
        this.dependencyCheckList.add(new FieldCutsceneAddCharacterDependencyCheck(characterId, partnerCharacterId, characterDirection, fieldCharacterDirection, characterPosition, cutscene));
    }
    
    public void AddEnableConversationDependencyCheck(String conversationId, NotificationAction notificationAction)
    {
        this.dependencyCheckList.add(new EnableConversationDependencyCheck(conversationId, notificationAction));
    }
    
    public void AddEnableEvidenceDependencyCheck(String evidenceId, NotificationAction notificationAction)
    {
        this.dependencyCheckList.add(new EnableEvidenceDependencyCheck(evidenceId, notificationAction));
    }
    
    public void AddUpdateEvidenceDependencyCheck(String evidenceId, String newEvidenceId, NotificationAction notificationAction)
    {
        this.dependencyCheckList.add(new UpdateEvidenceDependencyCheck(evidenceId, newEvidenceId, notificationAction));
    }
    
    public void AddDisableEvidenceDependencyCheck(String evidenceId, NotificationAction notificationAction)
    {
        this.dependencyCheckList.add(new DisableEvidenceDependencyCheck(evidenceId, notificationAction));
    }
    
    public void AddSetPartnerDependencyCheck(String partnerId, NotificationAction notificationAction)
    {
        this.dependencyCheckList.add(new SetPartnerDependencyCheck(partnerId, notificationAction));
    }
    
    public void AddEnableTopicDependencyCheck(String topicId, Confrontation confrontation, NotificationAction notificationAction)
    {
        this.dependencyCheckList.add(new EnableTopicDependencyCheck(topicId, confrontation, notificationAction));
    }
    
    public void AddBeginBranchOnConversationStateDependencyCheck(String conversationId)
    {
        this.dependencyCheckList.add(new BeginBranchOnConversationStateDependencyCheck(conversationId));
    }

    public void RunDependencyChecks() throws CaseParseException
    {
        for (int i = this.dependencyCheckList.size() - 1; i >= 0; i--)
        {
            this.dependencyCheckList.get(i).RunDependencyCheck();
            this.dependencyCheckList.remove(i);
        }
    }
    
    public void SetPrivateExponent(String privateExponent)
    {
        this.privateExponent = privateExponent;
    }
    
    public void SaveToXml(String filePath, String fileName) throws IOException, SlickException, CaseParseException
    {
        CaseXmlWriter writer = new CaseXmlWriter(new BufferedWriter(new FileWriter(filePath + "/" + fileName)));
        this.setFilePath(filePath);
        writer.StartElement("Case");
        
        this.animationManager.SaveToXml(writer);
        this.audioManager.SaveToXml(writer);
        this.content.SaveToXml(writer);
        this.dialogCharacterManager.SaveToXml(writer);
        this.dialogCutsceneManager.SaveToXml(writer);
        this.evidenceManager.SaveToXml(writer);
        this.fieldCharacterManager.SaveToXml(writer);
        this.fieldCutsceneManager.SaveToXml(writer);
        this.flagManager.SaveToXml(writer);
        this.partnerManager.SaveToXml(writer);
        this.spriteManager.SaveToXml(writer);
        
        writer.StartElement("ParentLocationListsBySpriteSheetId");
        
        for (String spriteSheetId : this.parentLocationListsBySpriteSheetId.keySet())
        {
            writer.StartElement("Entry");
            
            writer.WriteTextElement("SpriteSheetId", spriteSheetId);
            
            writer.StartElement("LocationList");
            
            for (String locationId : this.parentLocationListsBySpriteSheetId.get(spriteSheetId))
            {
                writer.StartElement("Entry");
                writer.WriteTextElement("LocationId", locationId);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.EndElement();
        }
        
        writer.EndElement();
        
        writer.StartElement("ParentLocationListsByVideoId");
        
        for (String videoId : this.parentLocationListsByVideoId.keySet())
        {
            writer.StartElement("Entry");
            
            writer.WriteTextElement("VideoId", videoId);
            
            writer.StartElement("LocationList");
            
            for (String locationId : this.parentLocationListsByVideoId.get(videoId))
            {
                writer.StartElement("Entry");
                writer.WriteTextElement("LocationId", locationId);
                writer.EndElement();
            }
            
            writer.EndElement();
            
            writer.EndElement();
        }
        
        writer.EndElement();
        
        writer.EndElement();
        writer.Close();
    }
    
    public void SaveMetadataToXml(String filePath, String fileName) throws IOException
    {
        CaseXmlWriter writer = new CaseXmlWriter(new BufferedWriter(new FileWriter(filePath + "/" + fileName)));
        this.metadata.SaveToXml(writer, filePath, this.privateExponent);
        writer.Close();
    }
    
    public static void LoadFromXmlAsync(String caseFilePath)
    {
        Case.instance.setFilePath(caseFilePath);
        
        if (caseParsingThread == null)
        {
            caseParsingThread = new Thread(new AsyncCaseParser(caseFilePath));
            caseParsingThread.setName("MLI Case Parsing Thread");
            caseParsingThread.setPriority(Thread.MIN_PRIORITY);
            caseParsingThread.start();
        }
        //new AsyncCaseParser(caseFilePath).run();
    }

    public static void LoadFromXml(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException, SlickException
    {
        Case.DiscardInstance();
        Case.instance.isFinished = false;
        
        CaseXmlReader reader = new CaseXmlReader(inputStream);
        reader.StartElement("Case");

        Case.instance.setLoadStage("animations");
        Case.instance.animationManager = new AnimationManager(reader);
        Case.instance.setLoadStage("audio");
        Case.instance.audioManager = new AudioManager(reader);
        Case.instance.setLoadStage("case information");
        Case.instance.content = new Content(reader);
        Case.instance.content.LoadFromXml(reader);
        Case.instance.setLoadStage("dialog resources");
        Case.instance.dialogCharacterManager = new DialogCharacterManager(reader);
        Case.instance.dialogCutsceneManager = new DialogCutsceneManager(reader);
        Case.instance.setLoadStage("evidence");
        Case.instance.evidenceManager = new EvidenceManager(reader);
        Case.instance.setLoadStage("field resources");
        Case.instance.fieldCharacterManager = new FieldCharacterManager(reader);
        Case.instance.fieldCutsceneManager = new FieldCutsceneManager(reader);
        Case.instance.setLoadStage("flags");
        Case.instance.flagManager = new FlagManager(reader);
        Case.instance.setLoadStage("partner information");
        Case.instance.partnerManager = new PartnerManager(reader);
        Case.instance.setLoadStage("sprite sheets");
        Case.instance.spriteManager = new SpriteManager();
        Case.instance.spriteManager.LoadFromXml(reader);
        
        reader.EndElement();
    }
    
    public void Reset()
    {
        this.content.Reset();
        this.evidenceManager.Reset();
        this.fieldCutsceneManager.Reset();
        this.flagManager.Reset();
        
        this.setIsFinished(false);
        this.setLoadStage("");
    }
    
    public void AddParentLocationForSpriteSheetId(String spriteSheetId, String locationId)
    {
        if (!this.parentLocationListsBySpriteSheetId.containsKey(spriteSheetId))
        {
            this.parentLocationListsBySpriteSheetId.put(spriteSheetId, new ArrayList<String>());
        }
        
        if (!this.parentLocationListsBySpriteSheetId.get(spriteSheetId).contains(locationId))
        {
            this.parentLocationListsBySpriteSheetId.get(spriteSheetId).add(locationId);
        }
    }
    
    public void AddParentLocationForVideoId(String videoId, String locationId)
    {
        if (!this.parentLocationListsByVideoId.containsKey(videoId))
        {
            this.parentLocationListsByVideoId.put(videoId, new ArrayList<String>());
        }
        
        if (!this.parentLocationListsByVideoId.get(videoId).contains(locationId))
        {
            this.parentLocationListsByVideoId.get(videoId).add(locationId);
        }
    }
    
    private interface DependencyCheck
    {
        public void RunDependencyCheck() throws CaseParseException;
    }
    
    private class FieldCutsceneAddCharacterDependencyCheck implements DependencyCheck
    {
        private String characterId;
        private String partnerCharacterId;
        private CharacterDirection characterDirection;
        private FieldCharacterDirection fieldCharacterDirection;
        private Vector2 characterPosition;
        private FieldCutscene cutscene;
        
        public FieldCutsceneAddCharacterDependencyCheck(String characterId, String partnerCharacterId, CharacterDirection characterDirection, FieldCharacterDirection fieldCharacterDirection, Vector2 characterPosition, FieldCutscene cutscene)
        {
            this.characterId = characterId;
            this.partnerCharacterId = partnerCharacterId;
            this.characterDirection = characterDirection;
            this.fieldCharacterDirection = fieldCharacterDirection;
            this.characterPosition = characterPosition;
            this.cutscene = cutscene;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            FieldCharacter character = null;

            if (Utils.equals(this.characterId, "PlayerCharacter"))
            {
                character = Case.getInstance().getFieldCharacterManager().GetPlayerCharacter();
            }
            else if (Utils.equals(this.characterId, "PartnerCharacter"))
            {
                character = Case.getInstance().getFieldCharacterManager().GetCharacterFromId(this.partnerCharacterId);
            }
            else
            {
                character = Case.getInstance().getFieldCharacterManager().GetCharacterFromId(this.characterId);
            }

            if (character == null)
            {
                throw new CaseParseException("No character with ID '" + this.characterId + "'.");
            }
            
            character = character.Clone();
            character.setDirection(this.characterDirection);
            character.setSpriteDirection(fieldCharacterDirection);
            character.setPosition(this.characterPosition);
            this.cutscene.AddCharacter(this.characterId, character);
            
            if (this.partnerCharacterId != null)
            {
                this.cutscene.setPartnerCharacterId(this.partnerCharacterId);
            }
        }
    }
    
    private class LocationCharacterDependencyCheck implements DependencyCheck
    {
        private String characterId;
        private CharacterDirection characterDirection;
        private FieldCharacterDirection characterSpriteDirection;
        private Vector2 characterPosition;
        private String clickId;
        private Vector2 interactionLocation;
        private boolean interactFromAnywhere;
        private Condition condition;
        private Location location;
        
        public LocationCharacterDependencyCheck(String characterId, CharacterDirection characterDirection, FieldCharacterDirection characterSpriteDirection, Vector2 characterPosition, String clickId, Vector2 interactionLocation, boolean interactFromAnywhere, Condition condition, Location location)
        {
            this.characterId = characterId;
            this.characterDirection = characterDirection;
            this.characterSpriteDirection = characterSpriteDirection;
            this.characterPosition = characterPosition;
            this.clickId = clickId;
            this.interactionLocation = interactionLocation;
            this.interactFromAnywhere = interactFromAnywhere;
            this.condition = condition;
            this.location = location;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            FieldCharacter character = Case.getInstance().getFieldCharacterManager().GetCharacterFromId(this.characterId);

            if (character == null)
            {
                throw new CaseParseException("No character with ID '" + this.characterId + "'.");
            }

            character.setDirection(this.characterDirection);
            character.setSpriteDirection(this.characterSpriteDirection);
            character.setPosition(this.characterPosition);
            
            if (this.clickId != null)
            {
                Encounter encounter = Case.getInstance().getContent().GetEncounterFromId(this.clickId);

                if (encounter != null)
                {
                    character.setClickEncounterId(this.clickId);
                }
                else      
                {
                    FieldCutscene cutscene = null;

                    for (FieldCutscene locationCutscene : location.getCutsceneList())
                    {
                        if (locationCutscene.getId().equals(this.clickId))
                        {
                            cutscene = locationCutscene;
                            break;
                        }
                    }

                    if (cutscene != null)
                    {
                        character.setClickCutsceneId(this.clickId);
                    }
                    else
                    {
                        throw new CaseParseException("No encounter or cutscene with ID '" + this.clickId + "'.");
                    }
                }
            }
            
            character.setInteractionLocation(this.interactionLocation);
            character.setInteractFromAnywhere(this.interactFromAnywhere);
            character.setCondition(this.condition);

            this.location.AddCharacter(character);
        }
    }
    
    private class EnableConversationDependencyCheck implements DependencyCheck
    {
        private String conversationId;
        private NotificationAction notificationAction;
        
        public EnableConversationDependencyCheck(String conversationId, NotificationAction notificationAction)
        {
            this.conversationId = conversationId;
            this.notificationAction = notificationAction;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            Conversation conversation = Case.getInstance().getContent().GetConversationFromId(this.conversationId);

            if (conversation == null)
            {
                throw new CaseParseException("No conversation exists with ID '" + this.conversationId + "'.");
            }

            if (notificationAction != null)
            {
                String rawNotificationText = "\"" + conversation.getName() + "\" added to ";

                if (conversation instanceof Confrontation)
                {
                    rawNotificationText += "Confrontations";
                }
                else if (conversation instanceof Interrogation)
                {
                    rawNotificationText += "Interrogations";
                }
                else if (conversation instanceof Conversation)
                {
                    rawNotificationText += "Questions";
                }
                else
                {
                    throw new CaseParseException("Unrecognized conversation type '" + conversation.getClass().toString() + "'.");
                }

                rawNotificationText += ".";
                notificationAction.setRawNotificationText(rawNotificationText);
            }
        }
    }
    
    private class EnableEvidenceDependencyCheck implements DependencyCheck
    {
        private String evidenceId;
        private NotificationAction notificationAction;
        
        public EnableEvidenceDependencyCheck(String evidenceId, NotificationAction notificationAction)
        {
            this.evidenceId = evidenceId;
            this.notificationAction = notificationAction;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            String evidenceName = Case.getInstance().getEvidenceManager().GetNameForId(evidenceId);

            if (evidenceName == null)
            {
                throw new CaseParseException("No evidence exists with ID '" + evidenceId + "'.");
            }
            
            String locationAdded = Case.getInstance().getEvidenceManager().IsEvidenceWithIdProfile(evidenceId) ? "profiles" : "evidence";

            if (notificationAction != null)
            {
                notificationAction.setRawNotificationText("\"" + evidenceName + "\" added to " + locationAdded + ".");
            }
        }
    }
    
    private class UpdateEvidenceDependencyCheck implements DependencyCheck
    {
        private String evidenceId;
        private String newEvidenceId;
        private NotificationAction notificationAction;
        
        public UpdateEvidenceDependencyCheck(String evidenceId, String newEvidenceId, NotificationAction notificationAction)
        {
            this.evidenceId = evidenceId;
            this.newEvidenceId = newEvidenceId;
            this.notificationAction = notificationAction;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            String evidenceName = Case.getInstance().getEvidenceManager().GetNameForId(evidenceId);
            String newEvidenceName = Case.getInstance().getEvidenceManager().GetNameForId(newEvidenceId);

            if (evidenceName == null)
            {
                throw new CaseParseException("No evidence exists with ID '" + evidenceId + "'.");
            }

            if (newEvidenceName == null)
            {
                throw new CaseParseException("No evidence exists with ID '" + newEvidenceId + "'.");
            }
            
            String locationUpdated = Case.getInstance().getEvidenceManager().IsEvidenceWithIdProfile(evidenceId) ? "profiles" : "evidence";

            if (notificationAction != null)
            {
                if (Utils.equals(evidenceName, newEvidenceName))
                {
                    notificationAction.setRawNotificationText("\"" + evidenceName + "\" updated in " + locationUpdated + ".");
                }
                else
                {
                    notificationAction.setRawNotificationText("\"" + evidenceName + "\" updated in " + locationUpdated + " to \"" + newEvidenceName + "\".");
                }
            }
        }
    }
    
    private class DisableEvidenceDependencyCheck implements DependencyCheck
    {
        private String evidenceId;
        private NotificationAction notificationAction;
        
        public DisableEvidenceDependencyCheck(String evidenceId, NotificationAction notificationAction)
        {
            this.evidenceId = evidenceId;
            this.notificationAction = notificationAction;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            String evidenceName = Case.getInstance().getEvidenceManager().GetNameForId(evidenceId);

            if (evidenceName == null)
            {
                throw new CaseParseException("No evidence exists with ID '" + evidenceId + "'.");
            }
            
            String locationRemoved = Case.getInstance().getEvidenceManager().IsEvidenceWithIdProfile(evidenceId) ? "profiles" : "evidence";

            if (notificationAction != null)
            {
                notificationAction.setRawNotificationText("\"" + evidenceName + "\" removed from " + locationRemoved + ".");
            }
        }
    }
    
    private class SetPartnerDependencyCheck implements DependencyCheck
    {
        private String partnerId;
        private NotificationAction notificationAction;
        
        public SetPartnerDependencyCheck(String partnerId, NotificationAction notificationAction)
        {
            this.partnerId = partnerId;
            this.notificationAction = notificationAction;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            if (!partnerId.equals("None"))
            {
                Partner partner = Case.getInstance().getPartnerManager().GetPartnerFromId(partnerId);

                if (partner == null)
                {
                    throw new CaseParseException("No partner exists with ID '" + partnerId + "'.");
                }
                
                Case.getInstance().getFieldCharacterManager().AddPartnerId(partnerId);

                if (notificationAction != null)
                {
                    notificationAction.setRawNotificationText(partner.getName() + " is now your partner!");
                }
            }
            else
            {
                if (notificationAction != null)
                {
                    notificationAction.setRawNotificationText("You no longer have a partner.");
                }
            }
        }
    }
    
    private class EnableTopicDependencyCheck implements DependencyCheck
    {
        private String topicId;
        private Confrontation confrontation;
        private NotificationAction notificationAction;
        
        public EnableTopicDependencyCheck(String topicId, Confrontation confrontation, NotificationAction notificationAction)
        {
            this.topicId = topicId;
            this.confrontation = confrontation;
            this.notificationAction = notificationAction;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            String topicName = this.confrontation.GetTopicNameById(topicId);

            if (topicName == null)
            {
                throw new CaseParseException("No topic exists with ID '" + topicId + "'.");
            }

            if (notificationAction != null)
            {
                notificationAction.setRawNotificationText("\"" + topicName + "\" added to topic list.");
            }
        }
    }
    
    private class BeginBranchOnConversationStateDependencyCheck implements DependencyCheck
    {
        private String conversationId;
        
        public BeginBranchOnConversationStateDependencyCheck(String conversationId)
        {
            this.conversationId = conversationId;
        }
        
        public void RunDependencyCheck() throws CaseParseException
        {
            Conversation conversation = Case.getInstance().getContent().GetConversationFromId(this.conversationId);

            if (conversation == null)
            {
                throw new CaseParseException("No conversation exists with ID '" + this.conversationId + "'.");
            }
        }
    }
    
    private static class AsyncCaseParser implements Runnable
    {
        private String caseFilePath;
        
        public AsyncCaseParser(String caseFilePath)
        {
            this.caseFilePath = caseFilePath;
        }
        
        public void run()
        {
            while (!GameUiLoader.getInstance().getIsUiLoaded())
            {
                try
                {
                    Thread.sleep(10);
                }
                catch (InterruptedException ex)
                {
                    Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            
            try
            {
                Debug.StartTimingOperation("Adding JAR file source");
                VirtualFileSystem.getInstance().addSource("JAR", JarVirtualFileSystemSource.getInstance());
                Debug.ReportTimeElapsed();
                Debug.StartTimingOperation("Opening JAR file");
                JarVirtualFileSystemSource.getInstance().openArchive(this.caseFilePath);
                Debug.ReportTimeElapsed();
                
                Case.LoadFromXml(VirtualFileSystem.getInstance().getInputStreamFromSource("JAR","case.xml"));
                
                if(!Settings.isMac)
                {
                    while (!RenderThread.getInstance().DoneLoadingImages())
                    {
                        try
                        {
                            Thread.sleep(10);
                        }
                        catch (InterruptedException ex)
                        {
                            Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                
                System.gc();
                CaseParsingEventProvider.RaiseCaseParsingComplete(Case.getInstance().getFilePath());
            }
            catch (ParserConfigurationException ex)
            {
                Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex);
            }
            catch (SAXException ex)
            {
                Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex);
            }
            catch (IOException ex)
            {
                Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex);
            }
            catch (SlickException ex)
            {
                Logger.getLogger(Case.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    public void markChildSprites() throws CaseParseException
    {
        this.content.markChildSprites();
        this.evidenceManager.markChildSprites();
        
        String playerCharacterId = Case.getInstance().getFieldCharacterManager().GetPlayerCharacterId();
        
        //Case.getInstance().getDialogCharacterManager().GetCharacterFromId(playerCharacterId).markChildSprites(Case.CommonFilesId);markInterjectionChildSprites
        Case.getInstance().getDialogCharacterManager().GetCharacterFromId(playerCharacterId).markInterjectionChildSprites(Case.CommonFilesId);
        
        for (FieldCharacterState state : FieldCharacterState.values())
        {
            for (FieldCharacterDirection direction : FieldCharacterDirection.values())
            {
                Case.getInstance().getFieldCharacterManager().GetCharacterFromId(playerCharacterId).markChildSprites(Case.CommonFilesId, state, direction);
            }
        }
        
        Case.getInstance().getDialogCharacterManager().markPartnerChildSprites();
        Case.getInstance().getFieldCharacterManager().markPartnerChildSprites();
        Case.getInstance().getPartnerManager().markPartnerChildSprites();
    }
}
